﻿using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Mercury.Core.Configuration;
using Mercury.Core.Const;
using Mercury.Core.DBManager;
using Mercury.Core.Enums;
using Mercury.Core.Extensions;
using Mercury.Core.ManageUser;
using Mercury.Entity.DomainModels;
using System.Reflection.Emit;
using System.Reflection.Metadata;
using System.Dynamic;
using System.Net.Http.Json;
using OfficeOpenXml.FormulaParsing.Excel.Functions.Math;

namespace Mercury.Core.Services
{
    /// <summary>
    /// 通过内置队列异步定时写日志
    /// </summary>
    public static class Logger
    {
        public static ConcurrentQueue<Sys_Log> loggerQueueData = new ConcurrentQueue<Sys_Log>();
        private static DateTime lastClearFileDT = DateTime.Now.AddDays(-1);
        private static string _loggerPath = AppSetting.DownLoadPath + "Logger\\Queue\\";
        static Logger()
        {

            Task.Run(() =>
            {
                Start();
                if (DBType.Name != "MySql")
                {
                    return;
                }
                try
                {
                    DBServerProvider.SqlDapper.ExcuteNonQuery("set global local_infile = 'ON';", null);
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"日志启动调用mysql数据库异常：{ex.Message},{ex.StackTrace}");
                }
            });
        }

        public static void Info(string message)
        {
            Info(LoggerType.Info, message);
        }
        public static void Info(LoggerType loggerType, string message = null)
        {
            Info(loggerType, message, null, null);
        }
        public static void Info(LoggerType loggerType, string requestParam, string resposeParam, string ex = null)
        {
            Add(loggerType, requestParam, resposeParam, ex, LoggerStatus.Info);
        }

        public static void OK(string message)
        {
            OK(LoggerType.Success, message);
        }
        public static void OK(LoggerType loggerType, string message = null)
        {
            OK(loggerType, message, null, null);
        }
        public static void OK(LoggerType loggerType, string requestParam, string resposeParam, string ex = null)
        {
            Add(loggerType, requestParam, resposeParam, ex, LoggerStatus.Success);
        }
        public static void Error(string message)
        {
            Error(LoggerType.Error, message);
        }
        public static void Error(LoggerType loggerType, string message)
        {
            Error(loggerType, message, null, null);
        }
        public static void Error(LoggerType loggerType, string requestParam, string resposeParam, string ex = null)
        {
            Add(loggerType, requestParam, resposeParam, ex, LoggerStatus.Error);
        }
        /// <summary>
        /// 多线程调用日志
        /// </summary>
        /// <param name="message"></param>
        public static void AddAsync(string message, string ex = null)
        {
            AddAsync(LoggerType.Info, null, message, ex, ex != null ? LoggerStatus.Error : LoggerStatus.Info);
        }
        public static void AddAsync(LoggerType loggerType, string requestParameter, string responseParameter, string ex, LoggerStatus status)
        {
            //var log = new Sys_Log()
            //{
            //    BeginDate = DateTime.Now,
            //    EndDate = DateTime.Now,
            //    User_Id = 0,
            //    UserName = "",
            //    //  Role_Id = ,
            //    LogType = loggerType.ToString(),
            //    ExceptionInfo = ex,
            //    RequestParameter = requestParameter,
            //    ResponseParameter = responseParameter,
            //    Success = (int)status
            //};
            //loggerQueueData.Enqueue(log);  
            Add(loggerType, requestParameter, responseParameter, ex, LoggerStatus.Error);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="requestParameter">请求参数</param>
        /// <param name="responseParameter">响应参数</param>
        /// <param name="success">响应结果1、成功,2、异常，0、其他</param>
        /// <param name="userInfo">用户数据</param>
        private static void Add(LoggerType loggerType, string requestParameter, string responseParameter, string ex, LoggerStatus status)
        {
            //对比属性，变更
            var Compare = (KeyValuePair<string, object> newValue, KeyValuePair<string, object> oldValue) =>
            {
                if (oldValue.Key == null)
                    return null;
                if (newValue.Value == oldValue.Value || newValue.Value?.ToString() == oldValue.Value?.ToString())
                    return null;
                if (oldValue.Value == null && string.IsNullOrEmpty(newValue.Value?.ToString()))
                    return null;
                if (newValue.Value == null && string.IsNullOrEmpty(oldValue.Value?.ToString()))
                    return null;
                dynamic modify = new
                {
                    Filed = oldValue.Key,
                    OldValue = oldValue.Value,
                    NewValue = newValue.Value
                };
                return modify;
            };

            //对比数据
            var CompareDic = (Dictionary<string, object> newDic, Dictionary<string, object> oldDic) =>
            {
                var list = new List<dynamic>();
                foreach (var item in newDic)
                {
                    var current = oldDic.FirstOrDefault(c => c.Key == item.Key);
                    var modify = Compare(item, current);
                    if (modify != null)
                        list.Add(modify);
                }
                return list;
            };

            //获取变更详情
            var GetModifyContent = () =>
            {
                var editModel = requestParameter.DeserializeObject<SaveModel>();
                if (editModel == null) return "";
                dynamic editContent = new ExpandoObject();
                editContent.Title = editModel.CnName;
                var keyProperty = editModel.MainData.FirstOrDefault(c => c.Key == editModel.MainKey);
                editContent.Key = keyProperty.Value;
                var list = CompareDic(editModel.MainData, editModel.CurrentMainData);

                var chanageList = new List<dynamic>();
                dynamic edit = new ExpandoObject();
                edit.Action = "编辑";
                var editDetails = new List<dynamic>();
                dynamic add = new ExpandoObject();
                add.Action = "新增";
                var addDetails = new List<dynamic>();
                if (editModel.DetailData != null)
                    foreach (var item in editModel.DetailData)
                    {
                        var detailKey = item[editModel.DetailKey];
                        if (detailKey == null || !editModel.CurrentDetailData.Any(c => c[editModel.DetailKey]?.ToString() == detailKey?.ToString()))
                        {
                            addDetails.Add(new
                            {
                                Content = item
                            });
                            continue;
                        }

                        var oldDic = editModel.CurrentDetailData.FirstOrDefault(c => c[editModel.DetailKey]?.ToString() == detailKey?.ToString());
                        var content = CompareDic(item, oldDic);
                        if (content.Any())
                            editDetails.Add(new
                            {
                                Key = detailKey,
                                Content = content
                            });
                    }
                if (!list.Any() && !editDetails.Any() && !addDetails.Any() && (editModel.DelKeys == null || !editModel.DelKeys.Any()))
                {
                    return null;
                }
                edit.ModifyData = editDetails;
                add.AddData = addDetails;
                if (list.Any())
                    editContent.MasterModify = new { Content = list };

                if (addDetails.Any())
                    chanageList.Add(add);

                if (editDetails.Any())
                    chanageList.Add(edit);

                if (editModel.DelKeys != null && editModel.DelKeys.Any())
                    chanageList.Add(new
                    {
                        Action = "删除",
                        Keys = editModel.DelKeys
                    });

                if (chanageList.Any())
                    editContent.DetailsModify = chanageList;
                var str = Newtonsoft.Json.JsonConvert.SerializeObject(editContent);
                return str;
            };

            UserInfo userInfo = UserContext.Current.UserInfo;
#if DEBUG
#else
            //生产环境不记录系统用户操作日志
            if (SystemUser.Enable && userInfo.User_Id == SystemUser.Instance.User_Id)
                return;
#endif
            Sys_Log log = null;
            try
            {
                var content = "";
                if (loggerType == LoggerType.Edit)
                {
                    content = GetModifyContent();
                    if (content == null)
                    {
                        Task.Run(() =>
                        {
                            try
                            {
                                WriteText($"未检测到更新数据,type:{loggerType.ToString()},reqParam:{requestParameter},respParam:{responseParameter}！");
                            }
                            catch (Exception)
                            {
                            }
                        });
                        return;
                    }
                }
                HttpContext context = Utilities.HttpContext.Current;
                if (context.Request.Method == "OPTIONS") return;
                ActionObserver cctionObserver = (context.RequestServices.GetService(typeof(ActionObserver)) as ActionObserver);
                if (context == null)
                {
                    WriteText($"未获取到httpcontext信息,type:{loggerType.ToString()},reqParam:{requestParameter},respParam:{responseParameter},ex:{ex},success:{status.ToString()}");
                    return;
                }
                log = new Sys_Log()
                {
                    BeginDate = cctionObserver.RequestDate,
                    EndDate = DateTime.Now,
                    User_Id = userInfo.User_Id,
                    UserName = userInfo.UserTrueName,
                    Role_Id = userInfo.Role_Id,
                    LogType = loggerType.ToString(),
                    ExceptionInfo = ex,
                    RequestParameter = requestParameter,
                    ResponseParameter = responseParameter,
                    Success = (int)status,
                    ModifyContent = content
                };

                SetServicesInfo(log, context);
            }
            catch (Exception exception)
            {
                log = log ?? new Sys_Log()
                {
                    BeginDate = DateTime.Now,
                    EndDate = DateTime.Now,
                    LogType = loggerType.ToString(),
                    RequestParameter = requestParameter,
                    ResponseParameter = responseParameter,
                    Success = (int)status,
                    ExceptionInfo = ex + exception.Message
                };
            }
            loggerQueueData.Enqueue(log);
        }



        private static async void Start()
        {
            DataTable queueTable = CreateEmptyTable();
            var config = AppSetting.LogConfig;
            var isEnableSubmeter = config != null && config.Enable && config.RealSubmaterStartDate.HasValue;
            var logs = new List<Sys_Log>();
            var tableName = "Sys_Log";
            var tempName = "";
            var excuteSplitLogTable = false;
            var currentDate = DateTime.Now;
            while (true)
            {
                try
                {
                    //为防止数据库代理服务异常，每天零点或者程序启动，执行一次分表
                    if ((DateTime.Now.Hour == 0 || (DateTime.Now - currentDate).Hours == 0) && !excuteSplitLogTable)
                    {
                        excuteSplitLogTable = true;
                        DBServerProvider.SqlDapper.ExcuteNonQuery("PLogSubmeterLogic", new { }, CommandType.StoredProcedure);
                    }
                    else if (DateTime.Now.Hour > 0 && (DateTime.Now - currentDate).Hours > 0)
                        excuteSplitLogTable = false;
                    if (loggerQueueData.Count > 0 && logs.Count < 500)
                    {
                        int count = loggerQueueData.Count;
                        for (int i = 0; i < count; i++)
                        {
                            loggerQueueData.TryDequeue(out Sys_Log log);
                            if (log == null)
                                break;
                            if (log.BeginDate == null || log.BeginDate?.Year < 2010)
                                log.BeginDate = DateTime.Now;
                            if (log.EndDate == null)
                                log.EndDate = DateTime.Now;
                            logs.Add(log);
                        }
                        continue;
                    }
                    //每5秒写一次数据
                    await Task.Delay(1000);
                    if (logs.Count == 0) { continue; }

                    if (!isEnableSubmeter || config.RealSubmaterStartDate.Value > DateTime.Now)
                    {
                        logs.ToDataTable(queueTable);
                        DBServerProvider.SqlDapper.BulkInsert(queueTable, tableName, SqlBulkCopyOptions.KeepIdentity, null, _loggerPath);
                        queueTable.Clear();
                        logs.Clear();
                    }
                    else
                    {
                        var nowDate = DateTime.Now.Date;
                        var ts = nowDate - config.RealSubmaterStartDate.Value;
                        var periods = ts.Days / config.Days;

                        var currentStart = config.RealSubmaterStartDate.Value.AddDays(periods * config.Days);
                        var currentEnd = config.RealSubmaterStartDate.Value.AddDays((periods + 1) * config.Days);

                        var minDate = logs.Min(c => c.BeginDate);
                        //日志包含最小时间小于当前周期内
                        if (minDate < currentStart)
                        {
                            if (periods > 0)
                            {
                                tempName = $"{tableName}_{currentStart.AddDays(-config.Days).ToLogTableNameStirng()}_{currentStart.ToLogTableNameStirng()}";
                            }
                            var tempList = logs.Where(c => c.BeginDate < currentStart).ToList();
                            if (tempList.Any())
                            {
                                tempList.ToDataTable(queueTable);
                                DBServerProvider.SqlDapper.BulkInsert(queueTable, tempName, SqlBulkCopyOptions.KeepIdentity, null, _loggerPath);
                                queueTable.Clear();
                            }
                        }
                        logs = logs.Where(c => c.BeginDate >= currentStart).ToList();
                        if (logs.Any())
                        {
                            tempName = $"{tableName}_{currentStart.ToLogTableNameStirng()}_{currentEnd.ToLogTableNameStirng()}";
                            logs.ToDataTable(queueTable);
                            DBServerProvider.SqlDapper.BulkInsert(queueTable, tempName, SqlBulkCopyOptions.KeepIdentity, null, _loggerPath);
                            queueTable.Clear();
                        }
                        logs.Clear();
                    }

                    if ((DateTime.Now - lastClearFileDT).TotalDays > 1)
                    {
                        Utilities.FileHelper.DeleteFolder(_loggerPath);
                        lastClearFileDT = DateTime.Now;
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"日志批量写入数据时出错:{ex.Message}");
                    WriteText(ex.Message + ex.StackTrace + ex.Source);
                    queueTable.Clear();
                }

            }
        }

        private static DataTable ToDataTable(this List<Sys_Log> list, DataTable dt)
        {
            list.ForEach(c => c.ToRow(dt));
            return dt;
        }

        private static void ToRow(this Sys_Log log, DataTable dt)
        {
            DataRow row = dt.NewRow();
            if (log.BeginDate == null || log.BeginDate?.Year < 2010)
            {
                log.BeginDate = DateTime.Now;
            }
            if (log.EndDate == null)
            {
                log.EndDate = DateTime.Now;
            }
            //  row["Id"] = log.Id;
            row["LogType"] = log.LogType;
            row["RequestParameter"] = log.RequestParameter?.Replace("\r\n", "");
            row["ResponseParameter"] = log.ResponseParameter?.Replace("\r\n", "");
            row["ExceptionInfo"] = log.ExceptionInfo;
            row["Success"] = log.Success ?? -1;
            row["BeginDate"] = log.BeginDate;
            row["EndDate"] = log.EndDate;
            row["ElapsedTime"] = ((DateTime)log.EndDate - (DateTime)log.BeginDate).TotalMilliseconds;
            row["UserIP"] = log.UserIP;
            row["ServiceIP"] = log.ServiceIP;
            row["BrowserType"] = log.BrowserType;
            row["Url"] = log.Url;
            row["User_Id"] = log.User_Id ?? -1;
            row["UserName"] = log.UserName;
            row["Role_Id"] = log.Role_Id ?? -1;
            row["ModifyContent"] = log.ModifyContent ?? "";
            dt.Rows.Add(row);
        }

        private static void WriteText(string message)
        {
            try
            {
                Utilities.FileHelper.WriteFile(_loggerPath + "WriteError\\", $"{DateTime.Now.ToString("yyyyMMdd")}.txt", message + "\r\n");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"日志写入文件时出错:{ex.Message}");
            }
        }

        private static void DequeueToTable(DataTable queueTable)
        {
            loggerQueueData.TryDequeue(out Sys_Log log);
            log.ToRow(queueTable);
        }
        private static DataTable CreateEmptyTable()
        {
            DataTable queueTable = new DataTable();
            queueTable.Columns.Add("LogType", typeof(string));
            queueTable.Columns.Add("RequestParameter", typeof(string));
            queueTable.Columns.Add("ResponseParameter", typeof(string));
            queueTable.Columns.Add("ExceptionInfo", typeof(string));
            queueTable.Columns.Add("Success", Type.GetType("System.Int32"));
            queueTable.Columns.Add("BeginDate", Type.GetType("System.DateTime"));
            queueTable.Columns.Add("EndDate", Type.GetType("System.DateTime"));
            queueTable.Columns.Add("ElapsedTime", Type.GetType("System.Int32"));
            queueTable.Columns.Add("UserIP", typeof(string));
            queueTable.Columns.Add("ServiceIP", typeof(string));
            queueTable.Columns.Add("BrowserType", typeof(string));
            queueTable.Columns.Add("Url", typeof(string));
            queueTable.Columns.Add("User_Id", Type.GetType("System.Int32"));
            queueTable.Columns.Add("UserName", typeof(string));
            queueTable.Columns.Add("Role_Id", Type.GetType("System.Int32"));
            queueTable.Columns.Add("ModifyContent", typeof(string));
            return queueTable;
        }

        public static void SetServicesInfo(Sys_Log log, HttpContext context)
        {
            log.Url = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase +
                context.Request.Path;

            log.UserIP = context.GetUserIp()?.Replace("::ffff:", "");
            log.ServiceIP = context.Connection.LocalIpAddress.MapToIPv4().ToString() + ":" + context.Connection.LocalPort;

            log.BrowserType = context.Request.Headers["User-Agent"];
            if (log.BrowserType != null && log.BrowserType.Length > 190)
            {
                log.BrowserType = log.BrowserType.Substring(0, 190);
            }
            if (string.IsNullOrEmpty(log.RequestParameter))
            {
                try
                {
                    log.RequestParameter = context.GetRequestParameters();
                    if (log.RequestParameter != null)
                    {
                        log.RequestParameter = HttpUtility.UrlDecode(log.RequestParameter, Encoding.UTF8);
                    }
                }
                catch (Exception ex)
                {
                    log.ExceptionInfo += $"日志读取参数出错:{ex.Message}";
                    Console.WriteLine($"日志读取参数出错:{ex.Message}");
                }
            }
        }
    }

    public enum LoggerStatus
    {
        Success = 1,
        Error = 2,
        Info = 3
    }
}
